#pragma once

#ifndef _SINGLETON_WORKER_QUEUE_THREAD_H_
#define _SINGLETON_WORKER_QUEUE_THREAD_H_

#include <memory>
#include <thread>
#include <queue>
#include <mutex>
#include <future>

class SingletonWorkerQueueThread {
public:
    using Task = std::function<void()>;

    static SingletonWorkerQueueThread& getInstance() {
        static SingletonWorkerQueueThread instance;
        return instance;
    };

    ~SingletonWorkerQueueThread();

    void stop();

    template<typename Callback, typename... TArgs>
    decltype(auto) enqueue(Callback&& f, TArgs&&... args)
    //auto enqueue(Callback&& f, TArgs&&... args) -> std::future<std::invoke_result_t<Callback, TArgs...>>
    {
        using ReturnType = std::invoke_result_t<Callback, TArgs...>;
        using FuncType = ReturnType(TArgs...);
        using Wrapped = std::packaged_task<FuncType>;
        std::shared_ptr<Wrapped> smartFunctionPointer = std::make_shared<Wrapped>(f);

        {
            std::unique_lock<std::mutex> lg{ mutex_ };
            task_queue_.emplace(
                [smartFunctionPointer, &args...]() -> void
                {
                    (*smartFunctionPointer)(std::forward<TArgs>(args)...);
                    return;
                }
            );
            cv_.notify_one();
        }
        return smartFunctionPointer->get_future();
    }

private:
    SingletonWorkerQueueThread();

    SingletonWorkerQueueThread(const SingletonWorkerQueueThread& other) = delete;
    SingletonWorkerQueueThread& operator=(const SingletonWorkerQueueThread&) = delete;

    void work_loop();

    std::unique_ptr<std::thread> worker_thread_;
    std::queue<Task> task_queue_;
    volatile bool running_ = true;
    std::mutex mutex_;
    std::condition_variable cv_;
};


#endif // _SINGLETON_WORKER_QUEUE_THREAD_H_
